package com.googlecode.lightest.core

import org.testng.reporters.XMLReporterConfig

import com.googlecode.lightest.core.tutorial.*

class TutorialTest extends GroovyTestCase {
    static final TEST_PASSED = XMLReporterConfig.TEST_PASSED
    static final TEST_FAILED = XMLReporterConfig.TEST_FAILED
    static final TASK_OK = "${ITaskResult.STATUS_OK}"
    static final TASK_FAILED = "${ITaskResult.STATUS_FAILED}"
    static final TASK_DOOMED = "${ITaskResult.STATUS_DOOMED}"
    
    static final TEMP_DIR = new File(System.getProperty('java.io.tmpdir'))
    static final OUTPUT_DIR = new File(TEMP_DIR, 'lightest-tutorial')
    static final RESULTS_XML = 'testng-results.xml'
    
    @Override
    void setUp() {
        OUTPUT_DIR.delete()
    }
    
    /**
     * Runs the suite specified by a resource path, using the given,
     * configuration, and returns the testng-results.xml file.
     * 
     * @param suitePath
     * @param configText
     */
    private Node runLightest(String suitePath, String configText = "") {
        def suiteStream = this.class.getResourceAsStream(suitePath)
        def suiteFile = createFileFromStream(suiteStream)
        
        return runLightest(suiteFile, configText)
    }
        
    /**
     * Runs the suite specified by a suite XML file, using the given,
     * configuration, and returns the Node representation of the
     * testng-results.xml file. The output directory is always overridden to be
     * OUTPUT_DIR.
     * 
     * @param suiteFile
     * @param configText
     */
    private Node runLightest(File suiteFile, configText = "") {
        def suites = [ suiteFile.getCanonicalPath() ]
        def testRunner = new TestRunner()
        
        testRunner.configure(configText)
        testRunner.setOutputDir(OUTPUT_DIR)
        testRunner.run(suites)
        
        def resultsFile = new File(testRunner.getOutputDir(), RESULTS_XML)
        
        return new XmlParser().parse(resultsFile)
    }
    
    /**
     * Creates and returns a temporary file using the contents of an input
     * stream. The file will be deleted when the JVM exits.
     *
     * @param stream  the stream whose contents to write to the file
     */
    private createFileFromStream(stream) {
        assert stream != null
        
        def file = File.createTempFile('lightest-tutorial', '.xml')
        file.deleteOnExit()
        file.write(stream.text)
        
        return file
    }
    
    private void assertPrefix(prefix, value) {
        assertEquals(prefix, value.substring(0, prefix.length()))
    }
    
    private void assertSuffix(suffix, value) {
        assertEquals(suffix, value.substring(value.length() - suffix.length()))
    }
    
    /**
     * Asserts that a given test method achieved a given status.
     * 
     * @param status
     * @param root        the root of the testng-results.xml document
     * @param methodName
     */
    private void assertTestMethodStatus(status, Node root, String methodName) {
        assertEquals(status, getTestMethod(root, methodName).'@status')
    }
    
    private getTestMethod(Node root, String methodName) {
        for (node in root.depthFirst()) {
            if (node.name() == 'test-method' && node.'@name' == methodName) {
                return node
            }
        }
    }
    
    /**
     * Returns a "List" of task-result nodes, in the order of appearance, for
     * a given test method. Only top level task-result nodes will be returned;
     * others may be reached by traversing from these nodes.
     * 
     * @param root        the root of the testng-results.xml document
     * @param methodName
     */
    private getTaskResults(Node root, String methodName) {
        return getTestMethod(root, methodName).'task-result'
    }
    
    void testTutorial1() {
        def suiteFile = LightestUtils.createSuiteFile([ Tutorial1.class ])
        def root = runLightest(suiteFile)
        def taskResults
        
        taskResults = getTaskResults(root, 'sayHello')
        
        assertTestMethodStatus(TEST_PASSED, root, 'sayHello')
        assertEquals(1, taskResults.size())
        assertSuffix('HelloWorld', taskResults[0].'@name')
        assertEquals(TASK_OK, taskResults[0].'@status')
        assertEquals("Said: Hello World!", taskResults[0].'@message')
        
        taskResults = getTaskResults(root, 'sayGreeting')

        assertTestMethodStatus(TEST_PASSED, root, 'sayGreeting')
        assertEquals(1, taskResults.size())
        assertSuffix('HelloWorld', taskResults[0].'@name')
        assertEquals(TASK_OK, taskResults[0].'@status')
        assertEquals("Said: Top of the mornin', world!", taskResults[0].'@message')
    }
    
    void testTutorial2() {
        def configText =
'''
config {
    envs (class: 'com.googlecode.lightest.core.tutorial.TutorialEnvironment') {
        env {
            id ('default')
            world ('Earth')
        }
    }
}
'''
        def suiteFile = LightestUtils.createSuiteFile([ Tutorial2.class ])
        def root = runLightest(suiteFile, configText)
        def taskResults
        
        taskResults = getTaskResults(root, 'sayGreeting')
        
        assertTestMethodStatus(TEST_PASSED, root, 'sayGreeting')
        assertEquals(2, taskResults.size())
        assertSuffix('QueryWorld', taskResults[0].'@name')
        assertEquals(TASK_OK, taskResults[0].'@status')
        assertEquals("Current World: Earth", taskResults[0].'@message')
        assertSuffix('HelloWorld', taskResults[1].'@name')
        assertEquals(TASK_OK, taskResults[1].'@status')
        assertEquals("Said: Top of the mornin', world!", taskResults[1].'@message')
        
        taskResults = getTaskResults(root, 'sayGreeting2')
        
        assertTestMethodStatus(TEST_PASSED, root, 'sayGreeting2')
        assertEquals(1, taskResults.size())
        assertSuffix('QueryWorld', taskResults[0].'@name')
        assertEquals(TASK_OK, taskResults[0].'@status')
        assertEquals('Find a world, and greet it internationally', taskResults[0].'@description')
        
        // the child results
        taskResults = taskResults[0].'nested-results'[0].'task-result'
        
        assertEquals(4, taskResults.size())
        Tutorial2.GREETINGS.eachWithIndex { g, i ->
            assertSuffix('HelloWorld', taskResults[i].'@name')
            assertEquals(TASK_OK, taskResults[1].'@status')
            assertEquals("Said: ${Tutorial2.GREETINGS[i]} (Current World: Earth)", taskResults[i].'@message')
        }
    }
    
    void testTutorial3() {
        def configText =
'''
config {
    envs (class: 'com.googlecode.lightest.core.tutorial.TutorialEnvironment') {
        env {
            id ('default')
            world ('Alderaan')
        }
    }
}
'''
        def suiteFile = LightestUtils.createSuiteFile([ Tutorial3.class ])
        def root = runLightest(suiteFile, configText)
        def taskResults
        
        taskResults = getTaskResults(root, 'sayGreeting')
        
        assertTestMethodStatus(TEST_FAILED, root, 'sayGreeting')
        assertEquals(2, taskResults.size())
        assertSuffix('QueryWorld2', taskResults[0].'@name')
        assertEquals(TASK_FAILED, taskResults[0].'@status')
        assertEquals("Unexpected exception: World not found: Alderaan", taskResults[0].'@message')
        assertPrefix('com.googlecode.lightest.core.tutorial.WorldNotFoundException: World not found: Alderaan', taskResults[0].'response-data'[0].text())
        assertEquals(0, taskResults[0].'nested-results'[0].'task-result'.size())
        assertSuffix('HelloWorld', taskResults[1].'@name')
        assertEquals(TASK_OK, taskResults[1].'@status')
        
        taskResults = getTaskResults(root, 'sayHelloBeforeChecking')
        
        assertTestMethodStatus(TEST_FAILED, root, 'sayHelloBeforeChecking')
        assertEquals(1, taskResults.size())
        assertSuffix('HelloWorld', taskResults[0].'@name')
        assertEquals(TASK_FAILED, taskResults[0].'@status')
        
        // the child results
        taskResults = taskResults[0].'nested-results'[0].'task-result'
        
        assertEquals(2, taskResults.size())
        assertSuffix('QueryWorld2', taskResults[0].'@name')
        assertEquals(TASK_FAILED, taskResults[0].'@status')
        assertEquals("Unexpected exception: World not found: Alderaan", taskResults[0].'@message')
        assertPrefix('com.googlecode.lightest.core.tutorial.WorldNotFoundException: World not found: Alderaan', taskResults[0].'response-data'[0].text())
        assertEquals(0, taskResults[0].'nested-results'[0].'task-result'.size())
        assertSuffix('HelloWorld', taskResults[1].'@name')
        assertEquals(TASK_OK, taskResults[1].'@status')
        assertEquals(0, taskResults[1].'nested-results'[0].'task-result'.size())
    }
    
    void testTutorial4() {
        def configText =
'''
config {
    envs (class: 'com.googlecode.lightest.core.tutorial.TutorialEnvironment') {
        env {
            id ('default')
            world ('Hades')
        }
    }
}
'''
        def suiteFile = LightestUtils.createSuiteFile([ Tutorial4.class ])
        def root = runLightest(suiteFile, configText)
        def taskResults
        
        taskResults = getTaskResults(root, 'sayHelloBeforeChecking')
        
        assertTestMethodStatus(TEST_FAILED, root, 'sayHelloBeforeChecking')
        assertEquals(1, taskResults.size())
        assertSuffix('HelloWorld', taskResults[0].'@name')
        assertEquals(TASK_DOOMED, taskResults[0].'@status')
        
        // the child results
        taskResults = taskResults[0].'nested-results'[0].'task-result'
        
        assertEquals(1, taskResults.size())
        assertSuffix('QueryWorld3', taskResults[0].'@name')
        assertEquals(TASK_DOOMED, taskResults[0].'@status')
        assertEquals("Unable to find more worlds: Hades", taskResults[0].'@message')
        assertEquals(0, taskResults[0].'nested-results'[0].'task-result'.size())
    }
}